package com.jse;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

import com.jse.json.Json;
import com.jse.json.JsonArray;
import com.jse.json.JsonObject;

public class Js implements ScriptEngine,Invocable,Compilable {

	public final static ScriptEngineManager SEM=new ScriptEngineManager();
//	public static String path=File.separatorChar=='/'?Js.class.getResource("/").getPath():Js.class.getResource("/").getPath().substring(1);//默认classpath
	public static boolean hot;//默认flase
	private ScriptEngine eng;
	
	static {
		System.setProperty("nashorn.args","-doe --language=es6 --optimistic-types=[true] --global-per-engine");//使用nashorn引擎时配置的参数
		System.setProperty("jvm.Dtruffle.js.NashornJavaInterop", "true");//TODO 
//		System.setProperty("polyglot.js.strict","true");//严格模式
		System.setProperty("polyglot.log.file","./jse.log");//js的日志
		System.setProperty("engine.WarnInterpreterOnly","false");//禁用警告
		System.setProperty("polyglot.js.commonjs-require", "true");//启用commonjs require('./foo.js')
		System.setProperty("polyglot.js.commonjs-require-cwd",Jse.jspath());//es模块默认的路径 Jse.conf文件 jse.js.path 注释则默认当前工作目录
		System.setProperty("polyglot.js.commonjs-core-modules-replacements","buffer:buffer/,path:path-browserify");//用于替换全局 Node.js内置的 npm 包
	}
	public static Js js=new Js();
	public static Bindings bindings;
	public Js() {
//		eng=SEM.getEngineByName("js");
		if(eng==null) {
			eng=(ScriptEngine)Refs.invokeMethod(Refs.newInstance("com.jse.nashorn.api.scripting.NashornScriptEngineFactory"),
					"getScriptEngine");
		}
		bindings = eng.getBindings(ScriptContext.ENGINE_SCOPE);
		if("OpenJDK Nashorn".equals(eng.getFactory().getEngineName())) {
			//特殊处理
		}else{
			bindings.put("polyglot.js.allowHostAccess", true);
			bindings.put("polyglot.js.allowNativeAccess", true);
			bindings.put("polyglot.js.allowCreateThread", true);
			bindings.put("polyglot.js.allowIO", true);
			bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);
			bindings.put("polyglot.js.allowHostClassLoading",true);
			bindings.put("polyglot.js.allowAllAccess",true);
			bindings.put("polyglot.js.nashorn-compat",true);//启用nashorn模式
			bindings.put("polyglot.js.ecmascript-version","2023");//默认latest为最新
			bindings.put("engine.WarnInterpreterOnly",false);//禁用警告
//			bindings.put("polyglot.js.syntax-extensions",true);//启用 Nashorn 语法扩展。（默认值：false）
//			bindings.put("js.foreign-object-prototype",true);//将 JavaScript 的默认原型提供给模仿 JavaScript 自身类型的外部对象（外部数组、对象和函数）默认false
//			bindings.put("js.intl-402",true);//启用 ECMAScript 国际化 API 默认false
//			bindings.put("js.strict",true);//启用严格模式 默认false
			//试验选项 Polyglot.eval('js','print("123")')
//			bindings.put("js.direct-byte-buffer",true);//对类型化数组使用直接（堆外）字节缓冲区 默认false
//			bindings.put("js.global-property",true);//提供“global”全局属性 默认false
//			bindings.put("js.import-assertions",true);//启用导入断言 默认false
//			bindings.put("js.json-modules",true);//启用json模块的加载 默认false
//			bindings.put("js.load-from-classpath",true);//允许“load”访问“classpath:” 默认false 不要与不受信任的代码一起使用。
//			bindings.put("js.load-from-url",true);//允许“load”访问 URL。默认false 不要与不受信任的代码一起使用。
//			bindings.put("js.operator-overloading",true);//启用运算符重载 默认false
//			bindings.put("js.performance",true);//提供”performance[性能]“全局属性 默认false
//			bindings.put("js.private-fields-in",true);//在运算符中启用私有字段（默认值：false）
//			bindings.put("js.regexp-match-indices",true);//开启 RegExp Match Indices 属性。默认false
//			bindings.put("js.scripting",true);//启用脚本功能（Nashorn 兼容性选项）。（默认值：false）
//			bindings.put("js.shebang",true);//允许解析以 #!. 开头的文件 (默认值:false)
//			bindings.put("js.shell",true);//为 js shell 提供全局函数。（默认值：false）
			
//			bindings.put("js.timezone","TimeZoneID");//设置自定义时区ID 默认为本地时区
//			bindings.put("js.v8-compat",true);//提供与 Google V8 引擎的兼容性。（默认值：false）
//			bindings.put("js.charset","UTF-8");//用于解码/<name>编码输入/输出流的字符集。
//			bindings.put("js.disable-eval",true);//用户代码不允许使用例如 eval（） 解析代码。（默认值：false）
//			bindings.put("js.error-cause",true);//启用错误原因提案。允许使用可选选项参数将错误与原因链接。（默认值：false）
//			bindings.put("js.esm-bare-specifier-relative-lookup",true);//解析相对于导入模块路径的 ESM 裸说明符，而不是尝试绝对路径查找。（默认值：假）
//			bindings.put("js.esm-eval-returns-exports",true);// 通过多语言API返回其导出符号的ES模块评估,从Java 使用 ES 模块时方便
//			bindings.put("js.function-constructor-cache-size",512);// 函数构造函数用于避免重新解析已知源的分析缓存的最大大小。（默认值：256）
//			bindings.put("js.locale","设置区域");//默认区域设置 
//			bindings.put("js.new-set-methods",true);//启用新的Set方法。（默认值：false）https://github.com/tc39/proposal-set-methods
//			bindings.put("js.temporal",true);//启用JavaScript Temporal API.（默认值：false）https://github.com/tc39/proposal-set-methods
//			bindings.put("js.top-level-await",true);//启用top-level-await. (默认值:false)
//			bindings.put("js.webassembly",true);//启用WebAssembly JavaScript API.（默认值：false）
//			bindings.put("js.script-engine-global-scope-import",true);//启用特定于 ScriptEngine 的全局范围导入函数.（默认值：false）
			bindings.put("javaObj", new Object());
		}
		eng.put("conf",Jse.conf);
		try {eng.eval(new InputStreamReader(Js.class.getResourceAsStream("/META-INF/jse.js")));}catch(ScriptException e){}
	}
	public static ScriptEngine newEngine() {return SEM.getEngineByName("js");}
	
	@Override
	public Object eval(String script, ScriptContext context) throws ScriptException {return eng.eval(script, context);}

	@Override
	public Object eval(Reader reader, ScriptContext context) throws ScriptException {return eng.eval(reader, context);}

	@Override
	public Object eval(String script) throws ScriptException {return eng.eval(script);}

	@Override
	public Object eval(Reader reader) throws ScriptException {return eng.eval(reader);}

	@Override
	public Object eval(String script, Bindings n) throws ScriptException {return eng.eval(script,n);}

	@Override
	public Object eval(Reader reader, Bindings n) throws ScriptException {return eng.eval(reader, n);}

	@Override
	public void put(String key, Object value) {eng.put(key, value);}

	@Override
	public Object get(String key) {return eng.get(key);}

	@Override
	public Bindings getBindings(int scope) {return eng.getBindings(scope);}

	@Override
	public void setBindings(Bindings bindings, int scope) {eng.setBindings(bindings, scope);}

	@Override
	public Bindings createBindings() {return eng.createBindings();}

	@Override
	public ScriptContext getContext() {return eng.getContext();}

	@Override
	public void setContext(ScriptContext context) {eng.setContext(context);}

	@Override
	public ScriptEngineFactory getFactory() {return eng.getFactory();}

	@Override
	public CompiledScript compile(String script) throws ScriptException {return ((Compilable)eng).compile(script);}

	@Override
	public CompiledScript compile(Reader script) throws ScriptException {return ((Compilable)eng).compile(script);}

	@Override
	public Object invokeMethod(Object t,String n,Object...a)throws ScriptException,NoSuchMethodException{
		if(t instanceof Function f)return f.apply(a);
		if("this".equals(n)){
			if(t.getClass().getSimpleName().equals("ScriptObjectMirror")){
			try {return Refs.findMethod(t,"call",MethodType.methodType(Object.class,Object.class,Object[].class)).bindTo(t).invoke("call",a);
			} catch (Throwable e) {
				throw new RuntimeException(e);
		}}}
		return ((Invocable)eng).invokeMethod(t, n, a);}

	@Override
	public Object invokeFunction(String n,Object...a) throws ScriptException, NoSuchMethodException{return ((Invocable)eng).invokeFunction(n, a);}

	@Override
	public <T> T getInterface(Class<T> clasz){return ((Invocable)eng).getInterface(clasz);}

	@Override
	public <T> T getInterface(Object thiz, Class<T> clasz){return ((Invocable)eng).getInterface(thiz,clasz);}
	
	public static void set(String k,Object v) {js.put(k,v);}
	public static <T>T get(String k,Class<T> c) {return (T)js.get(k);}

	public static Object evalFile(String f,Map m) {return execute(Fs.readString(f),m);}
	
	public static Object execute(String s) {
		return execute(s,null);
	}
	public static Object execute(String s,Map<String,Object> m) {
		try {
			if(m instanceof Bindings b)return js.eval(s,b);
			else if(m!=null)return js.eval(s,new SimpleBindings(m));
			return js.eval(s);
		} catch (ScriptException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	public static Tuple<CompiledScript,JsonObject> compile(Path p) {
		try {
			var s=Fs.readString(p);
			return new Tuple<CompiledScript,JsonObject>(js.compile(s),parsejsf(s));
		} catch (ScriptException e) {
			throw new RuntimeException(e);
		}
	}
	public static Tuple<CompiledScript,List<String>> compiles(Path p) {
		try {
			var s=Fs.readString(p);
			return new Tuple<CompiledScript,List<String>>(js.compile(s),names(s));
		} catch (ScriptException e) {
			throw new RuntimeException(e);
		}
	}
	
	private final static Map<String,Three<Path,Long,Tuple<CompiledScript,JsonObject>>> SCRIPTS=new HashMap<>();
	
	public static Object cFun(String p,String name,Object...args) {
		return cFun(Path.of(p),name,args);
	}
	public static Object cFun(Path path,String name,Object...args) {
		long time=Fs.lastModifiedTime(path);//获取文件更新时间
		Three<Path,Long,Tuple<CompiledScript,JsonObject>> cs=SCRIPTS.get(path.toString());
		if(cs==null||(!hot&&time!=cs.b().longValue())){//需要更新
			cs=new Three<Path,Long,Tuple<CompiledScript,JsonObject>>(path,time,compile(path));
			SCRIPTS.put(path.toString(),cs);
		}
		return cFun(path,cs.b(),cs.c(), name, args);
	}
	
	public static Object cFun(Path p,Long time,Tuple<CompiledScript,JsonObject> cs,String name,Object...args) {
		try {
			long ntime=Fs.lastModifiedTime(p);//获取文件更新时间
			if(!hot&&ntime>time) {
				cs=compile(p);
				SCRIPTS.put(p.toString(),new Three<Path,Long,Tuple<CompiledScript,JsonObject>>(p,time,cs));
			}
			cs.a().eval();
			return fun(name, args);}
		catch (ScriptException e) {throw new RuntimeException(e);}
	}
	public static Object fun(String name,Object...args) {
		try {
			return js.invokeFunction(name, args);
		} catch (NoSuchMethodException | ScriptException e) {
			throw new RuntimeException(e);
		}
	}
	
	public static <T>T to(Object som) {if("[object Array]".equals(som.toString()))return (T)((Map)som).values();return (T)som;}
	public static <T>T toJson(Object som) {return Json.json(to(som));}
	public static JsonArray parsejs(String code){return new JsonArray(fun("parsejsx",code).toString());}//parsejs是完整
	public static JsonObject parsejsf(String code){return new JsonObject(fun("parsejsf",code).toString());}//只有function
	
	private final static Pattern REG_NAME = Pattern.compile("function\\s(.*?)\\(");
	public static List<String> names(String code){
		List<String> names=new ArrayList<>();
		Matcher matcher = REG_NAME.matcher(code);
		while(matcher.find()){
			names.add(matcher.group(1).trim());
		}
		return names;
	}
	/**
	 * 
	 js.eval("""
var code=`function main(a){};var name={zs:1}`
var jtree=parse(code)
print(JSON.stringify(jtree.body))
				""")
	 */
	public static void main(String[] args) throws ScriptException, IOException, NoSuchMethodException, InterruptedException {
//		Job.setInterval(()->{
//			System.out.println(111);
//			var a=execute("""
//					import demo from "src/main/resources/demo.mjs"
//					let info  = demo()
//					console.log("-----js-------")
//					console.log(info)
//					console.log("-----js-------")
//					info
//					""");
//			System.out.println(a);
//		}, 3000);
//		try {
//			var a1=js.eval("function main(){return 1;}"+"\nmain");
//			
//			var a2=js.eval("function main(){return 2;} main");
//			js.eval("function main(){return 0}");
//			var a3=js.eval("function main(){return 3;} main");
//			js.eval("function main(){return 9}");
//			var a4=js.parsejs("""
//					var a=123;var b=main();
//					function main(){return 3;} function main1(x,a,c){return 4;} function main2(){}
//					""");
//			System.out.println(a4);
//			System.out.println(js.invokeMethod(a1,"this"));
//			System.out.println(js.invokeMethod(a2,"this"));
//			System.out.println(js.invokeMethod(a3,"this"));
//			js.compile("function main1(){return 11;};\nfunction main2(){return 22;};").eval();
//			System.out.println(fun("main1", args));
//			System.out.println(fun("main2", args));
//		} catch (ScriptException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//		var a=cFun(Path.of("D:\\work\\jsx\\src\\test\\resources\\a.js"),"main","5709");
//		System.out.println(a);
//		var som=to(a);
//		System.out.println(som);
		
//		Value value =null;
//        Source mysource =Source.newBuilder("js","""
//        		import {html} from 'src/main/resources/lit-html.js';
//// This is a lit-html template function. It returns a lit-html template.
//const helloTemplate = (name) => html`<div>Hello ${name}!</div>`;
//let o=helloTemplate('Steve');console.log(JSON.stringify(o));
//console.log(o.strings[0]+o.values[0]+o.strings[1])
//        		""","demoeeee").mimeType("application/javascript+module").build();
//        try (Context context = Context.newBuilder().allowAllAccess(true).build()) {
//        	context.eval("js","var document={createElement:function(s){return {innerHTML:'<'+s+'></'+s+'>'}},createTreeWalker:()=>{}};");
//            value = context.parse(mysource);
//            value.execute();
//        } catch (PolyglotException e) {
//            if (e.isSyntaxError()) {
//                SourceSection location = e.getSourceLocation();
//            } else {
//            }
//            throw e;
//        }
		var s="""
				function axxx(){
				}
				function xxx(){
				
				}
				""";
	}
	
	
	
}
